package com.jxpanda.spring.module.common;

import com.jxpanda.infrastructure.model.web.Response;
import com.jxpanda.spring.module.common.config.properties.ResponseWrapperProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.lang.NonNull;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;

/**
 * 自定义响应体处理类，继承自ResponseBodyResultHandler，用于处理响应体的转换和封装。
 */
@Slf4j
public class ResponseWrapper extends ResponseBodyResultHandler {

    private final static String ATTRIBUTE_PRODUCIBLE_MEDIA_TYPES = "org.springframework.web.reactive.HandlerMapping.producibleMediaTypes";

    private final ResponseWrapperProperties responseWrapperProperties;

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    private static final MethodParameter METHOD_PARAMETER;

    /*
     * 静态初始化块，用于初始化METHOD_PARAMETER，以便后续使用反射获取方法参数类型。
     * 这是一种技巧，用于在不创建实际方法的情况下模拟方法参数。
     */
    static {
        try {
            // 获得 METHOD_PARAMETER 。其中 -1 表示 `#methodForParams()` 方法的返回值
            METHOD_PARAMETER = new MethodParameter(
                    ResponseWrapper.class.getDeclaredMethod("methodForParams"), -1);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 模拟方法，仅用于初始化MethodParameter。
     * 实际上这个方法不会被调用。
     *
     * @return null，这个方法的存在只是为了能通过反射获取方法参数的类型信息。
     */
    private static Mono<Response<?>> methodForParams() {
        return null;
    }

    /**
     * 构造函数，初始化ResponseBodyResultHandler。
     *
     * @param writers  HTTP消息写入器列表，用于将对象转换为HTTP响应体。
     * @param resolver 请求内容类型解析器，用于确定客户端期望的响应内容类型。
     */
    public ResponseWrapper(List<HttpMessageWriter<?>> writers, RequestedContentTypeResolver resolver, ResponseWrapperProperties responseWrapperProperties) {
        super(writers, resolver);
        this.responseWrapperProperties = responseWrapperProperties;
    }

    /**
     * 构造函数，初始化ResponseBodyResultHandler，并注册ReactiveAdapterRegistry。
     *
     * @param writers  HTTP消息写入器列表，用于将对象转换为HTTP响应体。
     * @param resolver 请求内容类型解析器，用于确定客户端期望的响应内容类型。
     * @param registry 可响应式适配器注册表，用于处理反应式类型与Spring WebFlux支持的转换。
     */
    public ResponseWrapper(List<HttpMessageWriter<?>> writers, RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry, ResponseWrapperProperties responseWrapperProperties) {
        super(writers, resolver, registry);
        this.responseWrapperProperties = responseWrapperProperties;
    }

    /**
     * 处理处理程序结果，将返回值封装为Response对象。
     * 如果返回值是Mono或Flux类型，则会进一步处理为集合或单个元素的Response。
     *
     * @param exchange 服务器Web交换机，提供与请求和响应相关的上下文信息。
     * @param result   处理程序结果，包含处理程序方法的返回值。
     * @return Mono<Void>，表示异步处理完成后无返回值。
     */
    @NonNull
    @Override
    public Mono<Void> handleResult(@NonNull ServerWebExchange exchange, @NonNull HandlerResult result) {
        // FIXME: 有些接口是不能包装的，例如微信的回调接口，要按照微信的规则返回，此时要排除这些路径
        if (isIgnoreWrapper(exchange)) {
            return super.handleResult(exchange, result);
        }
        Object returnValue = result.getReturnValue();
        Object body;
        if (returnValue instanceof Mono<?> mono) {
            body = mono.map(ResponseWrapper::wrap)
                    .defaultIfEmpty(Response.ok());
        } else if (returnValue instanceof Flux<?> flux) {
            body = flux.collectList()
                    .map(ResponseWrapper::wrap)
                    .defaultIfEmpty(Response.ok(Collections.emptyList()));
        } else {
            body = wrap(returnValue);
        }
        return writeBody(body, METHOD_PARAMETER, exchange);
    }

    private boolean isIgnoreWrapper(ServerWebExchange exchange) {
        if (responseWrapperProperties.getIgnorePaths() != null && !responseWrapperProperties.getIgnorePaths().isEmpty()) {
            String urlPath = exchange.getRequest().getPath().value();
            List<String> ignoreWrapperPaths = responseWrapperProperties.getIgnorePaths();
            return ignoreWrapperPaths.stream().anyMatch(pattern -> pathMatcher.match(pattern, urlPath));
        }
        try {
            HashSet<MediaType> attribute = exchange.getAttribute(ATTRIBUTE_PRODUCIBLE_MEDIA_TYPES);
            return attribute != null && attribute.contains(MediaType.TEXT_EVENT_STREAM);
        } catch (Exception ignore) {

        }
        return false;
    }


    /**
     * 将给定的主体对象封装在Response对象中。
     * 如果对象已经是Response类型，则直接返回，否则创建一个新的Response对象。
     *
     * @param body 要封装的主体对象。
     * @return Response<?> 包装后的Response对象。
     */
    private static Response<?> wrap(Object body) {
        // 如果已经是 Response 类型，则直接返回
        if (body instanceof Response<?> response) {
            return response;
        }
        // 如果不是，则包装成 Response 类型
        return Response.ok(body);
    }
}
